package com.jay.media.music.ui.codec

import android.content.Context
import android.media.*
import android.os.Build
import android.text.TextUtils
import androidx.annotation.RequiresApi
import com.jay.codec.hard.CodecUtils
import com.jay.media.kit.LogHelper
import java.io.File
import java.nio.ByteBuffer
import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates

/**
 * @author xuzhou4
 * @date 2022/1/17
 */
class MP3Player {

    private val TAG = LogHelper.makeLogTag(MP3Player::class.java)

    private lateinit var audioTrack: AudioTrack
    private var audioSessionId by Delegates.notNull<Int>()

    private var path: String? = null
    private var mediaExtractor: MediaExtractor? = null
    private var decoder: MediaCodec? = null
    var timeoutUs = -1L
    private var mMinBufferSize = 0

    @RequiresApi(Build.VERSION_CODES.Q)
    fun play(context: Context, path: String) {
        val file = File(path)
        if (!file.exists()) {
            LogHelper.d(TAG, "play mp3 file fail, file not exist -> $path")
            return
        }

        for (mediaCodecInfo in CodecUtils.codecInfos().toList()) {
            if (!mediaCodecInfo.isEncoder && mediaCodecInfo.name.lowercase(Locale.getDefault())
                    .contains("mp3")
            ) {

                mediaCodecInfo.supportedTypes.forEach {
                    LogHelper.d(TAG, "support type : $it")
                }
            }
        }
        mediaExtractor = MediaExtractor()
        mediaExtractor?.let { it ->
            this.path = path
            it.setDataSource(path)
            LogHelper.d(TAG, "trackCount : ${it.trackCount}")
            for (i in 0 until it.trackCount) {
                val trackFormat = it.getTrackFormat(i)

                val decoderMime =
                    MediaCodecList(MediaCodecList.ALL_CODECS).findDecoderForFormat(trackFormat)
                LogHelper.d(TAG, "support format : $decoderMime")

                trackFormat.keys.forEach { key ->
                    LogHelper.d(TAG, "track format key : $key")
                }

                val mime = trackFormat.getString(MediaFormat.KEY_MIME) ?: ""
                LogHelper.d(TAG, "track mime : $mime")

                if (TextUtils.isEmpty(mime) || !mime.startsWith("audio/")) {
                    return
                }

                mediaExtractor?.selectTrack(i)

                val sampleRate = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)
                LogHelper.d(TAG, "track sampleRate : $sampleRate")

                val channelCount = trackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
                LogHelper.d(TAG, "track channelCount : $channelCount")

                val bitRate = trackFormat.getInteger("bits-per-sample")
                LogHelper.d(TAG, "track bitRate : $bitRate")

                //开始解码
                decoder = MediaCodec.createDecoderByType(mime)
                LogHelper.d(TAG, "decoder : ${decoder!!.name}")

                decoder?.let {
                    it.configure(
                        trackFormat,
                        null,
                        0,
                        null
                    )
                    it.outputFormat
                    it.start()
                }

                val outputFormat = decoder?.outputFormat
                outputFormat?.also {
                    it.keys.forEach { key ->
                        LogHelper.d(TAG, "track format out key : $key")
                    }
                    val outSampleRate = it.getInteger(MediaFormat.KEY_SAMPLE_RATE)
                    LogHelper.d(TAG, "track outSampleRate : $outSampleRate")

                    val outChannelCount = it.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
                    LogHelper.d(TAG, "track outChannelCount : $outChannelCount")

                    val encoding = it.getInteger("encoder-padding")
                    LogHelper.d(TAG, "track outEncoding : $encoding")

                    val audioFormat = AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(outSampleRate)
                        .setChannelIndexMask(if (outChannelCount >= 2) AudioFormat.CHANNEL_OUT_STEREO else AudioFormat.CHANNEL_OUT_MONO)
                        .build()


                    mMinBufferSize = AudioTrack.getMinBufferSize(
                        outSampleRate,
                        outChannelCount,
                        AudioFormat.ENCODING_PCM_16BIT
                    )

                    createAudioTrack(context, audioFormat, mMinBufferSize)
                }

                Thread(mDecodeRunnable).start()
                Thread(mPlayRunnable).start()
            }
        }
    }

    private var mDecodeRunnable = java.lang.Runnable { decode() }
    private var mPlayRunnable = java.lang.Runnable { play() }
    private val bufferInfo = MediaCodec.BufferInfo()

    private fun decode() {
        try {
            while (decoder != null) {
                LogHelper.d(TAG, "start decode ...")
                val dequeueInputBuffer = decoder!!.dequeueInputBuffer(timeoutUs)
                if (dequeueInputBuffer >= 0) {
                    val inputBuffer = decoder!!.getInputBuffer(dequeueInputBuffer)
                    if (inputBuffer != null) {
                        var sampleSize = mediaExtractor?.readSampleData(inputBuffer, 0) ?: -1
                        if (sampleSize < 0) {
                            LogHelper.d(TAG, "decode input buffer, end of stream")
                            decoder?.queueInputBuffer(
                                dequeueInputBuffer, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM
                            )
                            break
                        } else {
                            LogHelper.d(TAG, "decode readSampleData ： $sampleSize")
                            decoder?.queueInputBuffer(
                                dequeueInputBuffer, 0, sampleSize,
                                mediaExtractor?.sampleTime ?: 0, 0
                            )
                            mediaExtractor?.advance()
                        }
                    }


                } else {
                    LogHelper.d(TAG, "decode input buffer index fail")
                }
            }
            LogHelper.d(TAG, "decode finish, release ... ")
        } catch (e: Exception) {
            LogHelper.d(TAG, "decode exception, release ... ")
        } finally {
            mediaExtractor?.release()
            mediaExtractor = null
        }
    }


    private fun play() {
        try {
            val byteBuffer = ByteBuffer.allocate(mMinBufferSize / 2)
            var endOfStream = false
            while (decoder != null && !endOfStream) {
                decoder?.also {
                    var outputBufferIndex = decoder!!.dequeueOutputBuffer(bufferInfo, timeoutUs)
                    if (outputBufferIndex >= 0) {
                        val outputBuffer = decoder!!.getOutputBuffer(outputBufferIndex)
                        val byteArray = ByteArray(bufferInfo.size)
                        outputBuffer?.get(byteArray)
                        outputBuffer?.clear()

                        var pos = 0
                        var remain: Int
                        while (((byteArray.size - pos).also { remain = it }) > 0) {
                            var copyLen = min(remain, byteBuffer.remaining())
                            byteBuffer.put(byteArray, pos, copyLen)
                            pos += copyLen

                            if (byteBuffer.position() == mMinBufferSize / 2) {
                                byteBuffer.flip()
                                val ret = audioTrack.write(
                                    byteBuffer,
                                    mMinBufferSize / 2,
                                    AudioTrack.WRITE_BLOCKING
                                )
                                LogHelper.d(TAG, "audio track write : $ret")
                                byteBuffer.clear()
                            }
                        }

                        decoder?.releaseOutputBuffer(outputBufferIndex, false)
                    }

                    //所有帧都解码完之后退出循环
                    if (bufferInfo.flags != 0) {
                        LogHelper.d(TAG, "play finish")
                        endOfStream = true
                    }
                }
            }
        } catch (e: Exception) {
            LogHelper.d(TAG, "play exception, release ... ")
        } finally {
            decoder?.stop()
            decoder?.release()
            decoder = null
        }
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private fun createAudioTrack(context: Context, audioFormat: AudioFormat, minBufferSize: Int) {
        LogHelper.d(TAG, "mini buffer : $minBufferSize")

        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setLegacyStreamType(AudioManager.STREAM_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()

        var audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        audioSessionId = audioManager.generateAudioSessionId()
        LogHelper.d(TAG, "create audio sessionId : $audioSessionId")

        audioTrack = AudioTrack.Builder()
            .setAudioFormat(audioFormat)
            .setAudioAttributes(audioAttributes)
            .setTransferMode(AudioTrack.MODE_STREAM)
            .setBufferSizeInBytes(minBufferSize)
            .setSessionId(audioSessionId)
            .build()
        audioTrack.play()
    }
}